package utils

import (
	"errors"

	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"github.com/hashicorp/consul/api"
	"github.com/upmio/horus-polymerizer/log"

	"github.com/docker/libkv"
	"github.com/docker/libkv/store"
	"github.com/docker/libkv/store/consul"
)

//Plymer 聚合器信息
type Plymer struct {
	Pro  string `json:"pro"`
	IP   string `json:"ip"`
	Port string `json:"port"`

	//Name :this type must keep gloal unique,for consul,quepool,conpool all depend on it,and gererate by Ip+Port
	Name string `json:"name"`
}

const (
	//PlyerConsulDir is exproted
	PlyerConsulDir = "polymerizers/"
)

func init() {
	consul.Register()
}

var (
	consulcfg = api.DefaultConfig()
	kvclient  store.Store
)

//Initconsulcfg is exported
func Initconsulcfg(addr, dcenter string) error {
	consulcfg.Address = addr
	consulcfg.Datacenter = dcenter
	// Initialize a new store with consul
	kv, err := libkv.NewStore(
		store.CONSUL, // or "consul"
		[]string{addr},
		&store.Config{
			ConnectionTimeout: 10 * time.Second,
		},
	)
	if err != nil {
		return errors.New("Cannot create store consul")
	}

	kvclient = kv

	return nil
}

//GetKeyFromCluster is exported
func GetKeyFromCluster(key string) ([]byte, error) {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("GetKeyFromCluster PANIC:%v", err)
		}
	}()

	client, err := api.NewClient(consulcfg)
	if err != nil {
		return nil, err
	}

	// Get a handle to the KV API
	kv := client.KV()
	// Lookup the pair
	pair, _, err := kv.Get(key, nil)
	if err != nil {
		return nil, err
	}
	return pair.Value, nil
}

//DelKeyFromCluster is exported
func DelKeyFromCluster(key string) error {
	// Get a new client
	// cfg := api.DefaultConfig()
	defer func() {
		if err := recover(); err != nil {
			log.Errorf("delKeyFromCluster PANIC:%v", err)
		}
	}()

	client, err := api.NewClient(consulcfg)
	if err != nil {
		return err
	}

	// Get a handle to the KV API
	kv := client.KV()
	// Lookup the pair
	_, err = kv.Delete(key, nil)
	if err != nil {
		return err
	}
	return nil
}

// PutKeyToClusterWithTTL is exported
func PutKeyToClusterWithTTL(key string, value []byte, ttl time.Duration) error {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf(" PutKeyToClusterWithTTL PANIC:%v", err)
		}
	}()

	err := kvclient.Put(key, value, &store.WriteOptions{TTL: ttl * time.Second})
	if err != nil {
		errstr := fmt.Sprintf("Error trying to put value at key: %v\n", err)
		return errors.New(errstr)
	}

	return nil

}

//PutKeyToCluster is exported
func PutKeyToCluster(key string, value []byte) error {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("PutKeyToCluster PANIC:%v", err)
		}
	}()

	// Get a new client
	client, err := api.NewClient(consulcfg)
	if err != nil {
		return err
	}

	// Get a handle to the KV API
	kv := client.KV()

	// PUT a new KV pair
	p := &api.KVPair{Key: key, Value: value}
	_, err = kv.Put(p, nil)

	if err != nil {
		return err
	}

	return nil

}

//GetPlymerName is exported
func GetPlymerName(ip, port string) string {
	name := "plymer" + "-" + ip + ":" + port
	return name
}

//UnRegisterFromCluster is exported
func UnRegisterFromCluster(serviveID string) error {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("UnRegisterFromCluster PANIC:%v", err)
		}
	}()

	client, err := api.NewClient(consulcfg)
	if err != nil {
		return errors.New("create consul client error ")
	}
	agent := client.Agent()

	return agent.ServiceDeregister(serviveID)
}

//RegisterToCluster is exported
//TODO: 向consul 注册 需要抽象
func RegisterToCluster(name, addr, script string, port, interval, timeout int, tags []string) error {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("RegisterToCluster PANIC:%v", err)
		}
	}()

	client, err := api.NewClient(consulcfg)
	if err != nil {
		return errors.New("create consul client error ")
	}
	// intport, _ := strconv.Atoi(port)
	agent := client.Agent()
	check := &api.AgentServiceCheck{
		Script:   script,
		Interval: strconv.Itoa(interval) + "s",
		Timeout:  strconv.Itoa(timeout) + "s",
	}
	server := &api.AgentServiceRegistration{
		ID:      name,
		Name:    name,
		Tags:    tags,
		Port:    port,
		Address: addr,
		Check:   check,
	}
	err = agent.ServiceRegister(server)
	if err != nil {
		return err
	}

	return nil
}

//RegisterTCPToCluster is exported
func RegisterTCPToCluster(name, addr string, port, interval, timeout int, tags []string) error {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("RegisterTcpToCluster PANIC:%v", err)
		}
	}()

	client, err := api.NewClient(consulcfg)
	if err != nil {
		return errors.New("create consul client error")
	}
	// intport, _ := strconv.Atoi(port)
	agent := client.Agent()
	check := &api.AgentServiceCheck{
		TCP:      addr + ":" + strconv.Itoa(port),
		Interval: strconv.Itoa(interval) + "s",
		Timeout:  strconv.Itoa(timeout) + "s",
	}

	server := &api.AgentServiceRegistration{
		ID:      name,
		Name:    name,
		Tags:    tags,
		Port:    port,
		Address: addr,
		Check:   check,
	}
	err = agent.ServiceRegister(server)
	if err != nil {
		return err
	}

	return nil
}

//GetPlymersFromCluster is exported
func GetPlymersFromCluster() ([]*Plymer, error) {
	defer func() {
		if err := recover(); err != nil {
			log.Errorf("GetPlymersFromCluster PANIC:%v", err)
		}
	}()

	plyers := []*Plymer{}

	// Get a new client
	client, err := api.NewClient(consulcfg)
	if err != nil {
		return nil, err
	}
	// Get a handle to the KV API
	kv := client.KV()

	kvpairs, _, err := kv.List(PlyerConsulDir, nil)
	if err != nil {
		return nil, err
	}

	for _, pair := range kvpairs {
		plyer := Plymer{}
		err = json.Unmarshal(pair.Value, &plyer)
		if err != nil {
			log.Infof("%s json.Unmarshal:%v", err)
			continue
		}

		plyers = append(plyers, &plyer)

	}

	return plyers, nil
}

// func GetPlymersFromCluster(plyersPrefix string, plyerstag string) ([]*Plymer, error) {
// 	defer func() {
// 		if err := recover(); err != nil {
// 			log.Infof("GetPlymersFromCluster PANIC:%v", err)
// 		}
// 	}()
// 	//just for demo use
// 	plyers := []*Plymer{}
// 	f, err := os.Open("./plyer.config")
// 	if err != nil {
// 		return nil, err
// 	}

// 	defer f.Close()
// 	buf := bufio.NewReader(f)

// 	for {
// 		line, err := buf.ReadString('\n')

// 		if err != nil {
// 			if err == io.EOF {
// 				break
// 			}
// 			return nil, err
// 		}

// 		line = strings.TrimSpace(line)

// 		if line == "" || strings.HasPrefix(line, "#") {
// 			continue
// 		}

// 		strslice := strings.Split(line, ":")
// 		if len(strslice) != 5 {
// 			continue
// 		}

// 		newply := &Plymer{
// 			Pro:  strslice[0],
// 			Ip:   strslice[1],
// 			Port: strslice[2],
// 			Name: strslice[3],
// 			// Status: strslice[4],
// 		}

// 		plyers = append(plyers, newply)

// 	}
// 	return plyers, nil
// }

// func GetPlymersFromCluster(nodePrefix string, plyerstag string) ([]*Plymer, error) {
// 	client, err := api.NewClient(consulcfg)
// 	if err != nil {
// 		return nil, errors.New("create consul client error:")
// 	}

// 	catalog := client.Catalog()
// 	health := client.Health()

// 	var plyslice []*Plymer
// 	nodes, _, err := catalog.Nodes(nil)
// 	if err != nil {
// 		return nil, errors.New("get catalog/nodes error")
// 	}

// 	for _, node := range nodes {
// 		if strings.HasPrefix(node.Node, nodePrefix) {

// 			catalognode, _, err := catalog.Node(node.Node, nil)
// 			if err != nil {
// 				// return _, errors.New("get catalog/node  error")
// 				fmt.Println(node.Node, "get catalog/node  error ")
// 				continue
// 			}

// 			for servicename, server := range catalognode.Services {
// 				for _, tag := range server.Tags {
// 					if tag == plyerstag {
// 						ply := &Plymer{
// 							Pro:  "tcp",
// 							Ip:   server.Address,
// 							Port: strconv.Itoa(server.Port),
// 							Name: servicename,
// 						}

// 						plyslice = append(plyslice, ply)

// 						check, _, err := health.Checks(servicename, nil)
// 						if err != nil {
// 							fmt.Println("check err%v", err)
// 							continue
// 						}
// 						ply.Status = check[0].Status
// 						break
// 					}
// 				}
// 			}
// 		}
// 	}
// 	for _, ply := range plyslice {
// 		fmt.Println("uri::", ply)
// 	}

// 	return plyslice, nil
// }
